
from fullcontrol.geometry import point_to_polar, polar_to_point, arcXY, travel_to
from math import radians, tau, sin

def offset_path(points: list, offset: float, flip: bool = False, repeats: int = 1, arc_outer_corners: bool = False, arc_segments: int = 8, travel: bool = False, include_original: bool = False) -> list:
    ''' return an offset path (list of Points) defined by the parameter 'points' and 'offset'. parameter 'flip'
    changes the direction of the offset. multiple offset paths can be generated by setting 'repeats' > 1. 
    external corners can be given a radii by setting 'arc_outer_corners' as True. 'arc_segments' controls the
    number of segments for the newly created arcs. set 'travel' to True to travel to the first point in each
    offset path quickly without extruding, resulting in the returned list including Point and Extuder objects. 
    'include_original' can be used to include the original path as well as offset paths in the returned list.
    '''
    
    closed_path = True if abs(points[0].x - points[-1].x) < 0.00000001 and abs(points[0].y - points[-1].y) < 0.00000001 else False
    points_original = points
    if closed_path == True: 
        # add imaginary points for the closed paths before and after the first and last points
        points = [points[-2]] + points + [points[1]]

    # delete duplicated points (go in reverse order so the delete operation doesn't affect next iteration):
    point_count = len(points)
    for i in range(len(points)-1):
        if abs(points[point_count-i-1].x - points[point_count-i-2].x) < 0.00000001 and abs(points[point_count-i-1].y - points[point_count-i-2].y) < 0.00000001:
            del points[point_count-i-1]

    lines = [[points[i], points[i+1]] for i in range(len(points)-1)]
    directions = [point_to_polar(lines[i][1], lines[i][0]).angle for i in range(len(lines))]  # directions[i] is the polar angle of line i
    corner_angles_to_left = [(radians(180) - (directions[i+1]-directions[i])) % tau for i in range(len(directions)-1)] # i/e 180 degree minus how not-straight the two lines are
    corner_directions_to_left = [directions[i+1]+corner_angles_to_left[i]/2 for i in range(len(corner_angles_to_left))] # the direction of the line after the corner plus half of the corner angle


    if not flip: 
        corner_angles = corner_angles_to_left
        corner_directions = corner_directions_to_left
    else:
        corner_angles = [tau-corner_angles_to_left[i] for i in range(len(corner_angles_to_left))]
        corner_directions = [corner_directions_to_left[i]+tau/2 for i in range(len(corner_directions_to_left))]

    offset_points = []
    for repeat in range(repeats):
        point_for_this_repeat = []
        if not closed_path: point_for_this_repeat.append(polar_to_point(points[0], (offset*(repeat+1)), directions[0] + radians(90)))
        for i in range(len(corner_directions)):
            offset_now = (offset*(repeat+1))/sin((corner_angles_to_left[i]/2))
            if arc_outer_corners and corner_angles[i] > tau/2:
                if not flip:
                    point_for_this_repeat.extend(arcXY(points[i+1], offset*(repeat+1), directions[i]+tau/4, -(corner_angles[i]-tau/2), segments=arc_segments))
                else:
                    point_for_this_repeat.extend(arcXY(points[i+1], offset*(repeat+1), directions[i]-tau/4, (corner_angles[i]-tau/2), segments=arc_segments))
            else:
                point_for_this_repeat.append(polar_to_point(points[i+1], offset_now, corner_directions[i]))
        if not closed_path: point_for_this_repeat.append(polar_to_point(points[-1], (offset*(repeat+1)), directions[-1] + radians(90))) 
        if closed_path and arc_outer_corners and corner_angles[-1] > tau/2: point_for_this_repeat = point_for_this_repeat[0:-arc_segments] # delete the duplicate arc for a closed path (only move to the start point of the arc to close the path)
        if travel: offset_points.extend(travel_to(point_for_this_repeat[0]))
        # for step in offset_points: print(repr(step))
        offset_points.extend(point_for_this_repeat)
    if include_original: 
        return points_original + offset_points
    else:
        return offset_points